/*
 * To change this template, choose Tools | Templates
 * and open the template in the editor.
 */

package org.maze.Population;

import java.util.ArrayList;
import java.util.Random;
import org.maze.Maze;
import org.maze.configuration.IndividualConfiguration;
import org.maze.individual.Individual;
import org.maze.sort.Sort;

/**
 *
 * @author Thomas
 */
public class Village {
    private ArrayList<Individual> villagers = new ArrayList<Individual>();
    private int size;
    private double averageScore;
    private double averageDiversity;
    private Individual fitest;
    private static final Random rand = new Random();

    public Village(IndividualConfiguration indivConf, int size) throws Exception{
        this.size=size;
        for(int i=0; i<size; i++){
            byte[] genome = Individual.fillBlank(new byte[Maze.SIZE*Maze.SIZE], 0);
            villagers.add(new Individual(genome, indivConf));
        }
    }

    public Village(ArrayList<Individual> villagers){
        this.size = villagers.size();
        this.villagers=villagers;
    }

    public Individual getFitest(){
        return fitest;
    }

    public int getSize(){
        return size;
    }

    public ArrayList<Individual> getVillagers(){
        return villagers;
    }

    public void setVillages(ArrayList<Individual> v){
        villagers = v;
    }

    public double getAverageScore(){
        return averageScore;
    }

    public double getAverageDiversity(){
        return averageDiversity;
    }

    public void computeStats(){
        int nbScore=0;
        int nbDiv=0;
        averageScore = 0;
        averageDiversity = 0;
        for(int i=0; i<size; i++){
            double fitnessScore = villagers.get(i).getFitnessScore();
            averageScore += fitnessScore;
            nbScore++;
            for(int j=0; j<size; j++){
                if(i!=j){
                    averageDiversity += villagers.get(i).getDiversity(villagers.get(j));
                    nbDiv++;
                }
            }
        }
        averageDiversity/=nbDiv;
        averageScore/=nbScore;
    }
    
    public Thread getThread(){
        return new NextGenerationThread(this);
    }

    private class NextGenerationThread extends Thread{
        private Village village;

        public NextGenerationThread(Village v){
            village = v;
        }

        @Override
        public void run(){
            village.computeStats();
            ArrayList<Individual> strongers = this.eliminWeakest();

            ArrayList<Individual> newPop = new ArrayList<Individual>();
            Object[] individuals = strongers.toArray();
            Sort.QuickSort(individuals);
            fitest = (Individual)individuals[0];
            newPop.add(fitest);
            for(int j=0; j < individuals.length; j++){
                    Individual[] children = this.cross((Individual)individuals[j], village.getVillagers());
                    newPop.add(children[0]);
                    newPop.add(children[1]);
                    if(j<individuals.length/2){
                        children = this.cross((Individual)individuals[j], village.getVillagers());
                        newPop.add(children[0]);
                        newPop.add(children[1]);
                    }
                    if(j<individuals.length/3){
                        children = this.cross((Individual)individuals[j], village.getVillagers());
                        newPop.add(children[0]);
                        newPop.add(children[1]);
                    }
                    if(j<individuals.length/5){
                        children = this.cross((Individual)individuals[j], village.getVillagers());
                        newPop.add(children[0]);
                        newPop.add(children[1]);
                    }
                    if(j<individuals.length/10){
                        children = this.cross((Individual)individuals[j], village.getVillagers());
                        newPop.add(children[0]);
                        newPop.add(children[1]);
                    }
            }

            if(newPop.size()<=village.getSize()){
                int val = newPop.size();
                for(int i = 0; i < village.getSize() - val; i++){
                    newPop.add(village.getVillagers().get(rand.nextInt(village.getSize())));
                }
            }else{
                individuals = newPop.toArray();
                Sort.QuickSort(individuals);
                newPop = new ArrayList<Individual>();
                for(int i = 0; i<village.getSize(); i++){
                    newPop.add((Individual)individuals[i]);
                }
            }
            village.setVillages(newPop);
        }

        public ArrayList<Individual> eliminWeakest(){
            ArrayList<Individual> newPop = new ArrayList<Individual>();
            double min_score = 0.5*village.getAverageScore();
            Object[] individuals =  village.getVillagers().toArray();
            Sort.QuickSort(individuals);
            newPop.add(((Individual)individuals[0]));
            for(int j = 1; j < individuals.length/4; j++){
                double fitnessScore = ((Individual)individuals[j]).getFitnessScore();
                if(fitnessScore>min_score){
                   newPop.add(((Individual)individuals[j]));
                }else{
                    break;
                }
            }
            return newPop;
        }

        public Individual[] cross(Individual father, ArrayList<Individual> pop){
            double diff;
            Individual mother=null;
            int count=0;
            do{
                mother = pop.get(rand.nextInt(pop.size()));
                diff = father.getDiversity(mother);
                count++;
            }while(diff < 0.2 && count < 100 );
            Individual[] children = father.cross(mother);
            try {
                children[0].mutate();
                children[1].mutate();
            }catch (Exception ex){
                ex.printStackTrace();
            }
            try {
                children[0].improve();
                children[1].improve();
            } catch (Exception ex) {
                ex.printStackTrace();
            }
            return children;
        }
    }
}
